#include <QtGui>
#include <cmath>
#include "renderarea.h"

const double ZoomOutFactor = 0.8f;
const double ZoomInFactor = 1 / ZoomOutFactor;
const int ScrollStep = -20;

RenderArea::RenderArea(RenderThread *thread, QWidget *parent)
    : QWidget(parent), renderThread(thread), lambdaComplex(0, 0), iteration(0), rendering(true)
{
//     setBackgroundRole(QPalette::Base);
//     setAutoFillBackground(false);
//     setAttribute(Qt::WA_OpaquePaintEvent);
//     setAttribute(Qt::WA_NoSystemBackground);

    setFocusPolicy(Qt::StrongFocus);
    setMouseTracking(true);
    qRegisterMetaType<QImage>("QImage");
    connect(renderThread, SIGNAL(renderedImage(const QImage &)),
            this, SLOT(updatePixmap(const QImage &)));

    setCursor(Qt::CrossCursor);
}

QSize RenderArea::minimumSizeHint() const
{
    return QSize(100, 100);
}

QSize RenderArea::sizeHint() const
{
    return QSize(400, 400);
}

void RenderArea::setIteration(int i)
{
    iteration = i;
    render();
}

void RenderArea::setLambda(double x, double y)
{
    lambdaComplex = Complex(x, y);
    render();
}

Complex RenderArea::lambda() const
{
    return lambdaComplex;
}

void RenderArea::setRenderThread(RenderThread *thread)
{
    renderThread = thread;
}

void RenderArea::reset()
{
    projectionMatrix.reset();
    QResizeEvent event(size(), size());
    QCoreApplication::sendEvent(this, &event);
}

void RenderArea::render() {
    rendering = true;
    update();
    renderThread->render(iteration, lambdaComplex, invertedMatrix, size());
}

void RenderArea::paintEvent(QPaintEvent * /* event */)
{
    QPainter painter(this);
    painter.fillRect(rect(), Qt::black);
    painter.setPen(palette().dark().color());
    painter.setBrush(Qt::NoBrush);
    painter.drawRect(QRect(0, 0, width() - 1, height() - 1));

    painter.save();
    painter.setTransform(pixmapMatrix);
    painter.drawPixmap(0,0, pixmap);
    painter.restore();

    if (rendering) {
        QString text = tr("Rendering, please wait...");
        QFontMetrics metrics = painter.fontMetrics();
        int textWidth = metrics.width(text);

        painter.setPen(Qt::NoPen);
        painter.setBrush(QColor(0, 0, 0, 127));
        painter.drawRect((width() - textWidth) / 2 - 5, (height() - metrics.lineSpacing())/2 - 2, textWidth + 10,
                         metrics.lineSpacing() + 5);
        painter.setPen(Qt::white);
        painter.drawText(rect(), Qt::AlignCenter, text);
        return;
    }
}

void RenderArea::resizeEvent(QResizeEvent *event)
{
    bool keepAspect = true;
    double left;
    double right;
    double bottom;
    double top;
    if (keepAspect || projectionMatrix.isIdentity()) {
        left = -2;
        right = 2;
        bottom = -2;
        top = 2;
    }
    else {
        invertedMatrix.map(0,0, &left, &top);
        invertedMatrix.map(event->oldSize().width(), event->oldSize().height(), &right, &bottom);
    }

    if (keepAspect) {
        double aspect = (double)width()/height();
        if (width() >= height()) {
            left *= aspect;
            right *= aspect;
        }
        else {
            bottom /= aspect;
            top /= aspect;
        }
    }

    projectionMatrix.reset();
    invertedMatrix = projectionMatrix
        .scale((double)width()/(right-left), -(double)height()/(top-bottom))
        .translate(-left, -top)
        .inverted();

    render();
}

void RenderArea::keyPressEvent(QKeyEvent *event)
{
    switch (event->key()) {
    case Qt::Key_Plus:
        zoom(ZoomInFactor);
        break;
    case Qt::Key_Minus:
        zoom(ZoomOutFactor);
        break;
    case Qt::Key_Left:
        scroll(-ScrollStep, 0);
        break;
    case Qt::Key_Right:
        scroll(+ScrollStep, 0);
        break;
    case Qt::Key_Down:
        scroll(0, -ScrollStep);
        break;
    case Qt::Key_Up:
        scroll(0, +ScrollStep);
        break;
    default:
        QWidget::keyPressEvent(event);
    }
}

void RenderArea::wheelEvent(QWheelEvent *event)
{
    int numDegrees = event->delta() / 8;
    double numSteps = numDegrees / 15.0f;
    zoom(pow(ZoomInFactor, numSteps));
}

void RenderArea::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
        lastDragPos = event->pos();
    if (event->buttons() & Qt::RightButton) {
        QPointF pos = QPointF(event->pos())*invertedMatrix;
        emit positionChanged(pos.x(), pos.y());
    }
}

void RenderArea::mouseMoveEvent(QMouseEvent *event)
{
    if (event->buttons() & Qt::LeftButton) {
        QPoint delta = lastDragPos - event->pos();
        lastDragPos = event->pos();
        scroll(delta.x(), delta.y());
    } else {
        curPos = event->pos();
        QPointF newpos = QPointF(event->pos())*invertedMatrix;
        QToolTip::showText(event->globalPos(), QString("(%3, %4)")
                       .arg(newpos.x())
                       .arg(newpos.y()), this);
    }
}

void RenderArea::mouseReleaseEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        QPoint delta = lastDragPos - event->pos();
        lastDragPos = QPoint();
        scroll(delta.x(), delta.y());
    }
}

void RenderArea::updatePixmap(const QImage &image)
{
    if (!lastDragPos.isNull())
        return;

    pixmap = QPixmap::fromImage(image);
    rendering = false;
    lastDragPos = QPoint();
    pixmapMatrix.reset();
    update();
}

void RenderArea::zoom(double zoomFactor)
{
    double centerX, centerY;

    invertedMatrix.map(curPos.x(), curPos.y(), &centerX, &centerY);
    invertedMatrix = projectionMatrix
        .translate(centerX, centerY)
        .scale(zoomFactor, zoomFactor)
        .translate(-centerX, -centerY)
        .inverted();

    pixmapMatrix.inverted().map(curPos.x(), curPos.y(), &centerX, &centerY);
    pixmapMatrix
        .translate(centerX, centerY)
        .scale(zoomFactor, zoomFactor)
        .translate(-centerX, -centerY);

    render();
}

void RenderArea::scroll(int deltaX, int deltaY)
{
    invertedMatrix.translate(deltaX, deltaY);
    projectionMatrix = invertedMatrix.inverted();

    pixmapMatrix = pixmapMatrix.inverted().translate(deltaX, deltaY).inverted();

    render();
}
